/*

 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h>	/* printk() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/delay.h>	/* udelay */
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/poll.h>
#include <linux/wait.h>

#include <asm/io.h>

#define LCD_NR_PORTS	8	/* use 8 ports by default */

#define LCD_MASK 0x0B
#define LCD_E 0x08
#define LCD_EN 0x09
#define LCD_RS 0x0C

#define LCD_CLEAR 0x01
#define LCD_HOME 0x02
#define LCD_ENTRY 0x04 // this will have to be or to shift right or left
#define LCD_OFF 0x08
#define LCD_ON 0x0C
#define LCD_CURSOR_ON 0x02
#define LCD_BLINK_ON 0x01
#define LCD_CUR_SHIFT_LEFT 0x10
#define LCD_CUR_SHIFT_RIGHT 0x14
#define LCD_FCNT_SET 0x38

static char lcd_e = 0x08;
static char lcd_en = 0x09;
static char lcd_rs = 0x0C;

void do_scroll(void);

int columns[80]={ 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,
		  0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,
		  0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
		  0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67};

struct timer_list timer;
char *buf;
int temporary = 0; // this is used for the scrolling. is where the string actually starts (w/o flags)
int second_start=0;
int scrolling = 0; // 0 means is off
int current_start;
int current_display=0,second_display=0;
int current_line;
/*
 * all of the parameters have no "lcd_" prefix, to save typing when
 * specifying them at load time
 */
static int major = 0;	/* dynamic by default */
module_param(major, int, 0);

//static int use_mem = 0;	/* default is I/O-mapped */
//module_param(use_mem, int, 0);

/* default is the first printer port on PC's. "lcd_base" is there too
   because it's what we want to use in the code */
static unsigned long base = 0x378;
unsigned long lcd_base = 0;
module_param(base, long, 0);
int minor;
struct inode inode;
struct file filp;
int count=0,moveby=0;;
unsigned long port = 0x378 ;
int column=0;
char temp='0';
//char *buf;

void write_command(char);
MODULE_AUTHOR ("Alessandro Rubini");
MODULE_LICENSE("Dual BSD/GPL");


int parse_cmd(char * t)
{
    int i=0,j=0;
    if(t[0]=='-') {
	printk(KERN_INFO "flag sets\n");
	//check scrolling
	if(scrolling==1)
	    {
		if(!((t[i+1] == 'S' || t[i+1] == 's')
		     && (t[i+2] == 'C' || t[i+2] == 'c')
		     && (t[i+3] == 'R' || t[i+3] == 'r')
		     && (t[i+4] == 'O' || t[i+4] == 'o') 
		     && (t[i+5] == 'F' || t[i+5] == 'f')))
		    return -1;
		
	    }
	for(i=0;i<count;i++)
	    {
		if(t[i] == '-'){
		    if(t[i+1]=='L' || t[i+1]=='l') {
			//printk(KERN_INFO "Line jump to %d\n",t[i+1]);
			printk(KERN_INFO "Line jump-ing\n");
			if(t[i+2]=='1') {
			    column = 0;
			    write_command(0x80);
			}
			else if (t[i+2]=='2') {
			    column = 20;
			    write_command(0xC0);
			}
			else if (t[i+2]=='3') {
			    column = 40;
			    write_command(0x94);
			}
			else if(t[i+2]=='4') {
			    column = 60;
			    write_command(0xD4);
			}
			j=i+4;
			printk(KERN_INFO " j=%d\n",j);	
		    } // end of move line cmd
		    else if(t[i+1]=='M' || t[i+1]=='m'){
			if(t[i+3]==' ' || count==3) {
			    //single move
			    moveby=1;
			    printk(KERN_INFO "Single Move\n");
			    j=i+3;
			}
			else if((t[i+4]==' ' || count==4 || t[i+4] == '\n') && t[i+3]!=' ' ) {
			    //sigle digit specified
			    moveby=(int)(t[i+3]-'0');
			    printk(KERN_INFO "Single move by %d\n",moveby);
			    j=i+5;
			}
			else if(t[i+4]!=' ' && (t[i+5]==' '||count==5 || t[i+5] == '\n')){
			    //Two digits specified
			    moveby=(int)(t[i+3]-'0');
			    moveby=10*moveby;
			    moveby+=(int)(t[i+4]-'0');
			    printk(KERN_INFO "Double move ny %d\n",moveby);
			    j=i+6;
			}
			else if(t[i+4]!=' ' && (t[i+5]!=' ' || count>5)  && t[i+3]!=' '){
			    //three of more digits specified = FAIL
			    printk(KERN_INFO "move by fail\n");
			}
			if(t[i+2]=='R' || t[i+2]=='r'){
			    printk(KERN_INFO "Move right by = %d\n",moveby);
			    write_command(LCD_ON);
			    column+=moveby;
			    column%=80;
			    write_command(0x80^columns[column]);
			    write_command(LCD_ON | LCD_CURSOR_ON);
			}
			else if(t[i+2]=='L' || t[i+2]=='l'){
			    printk(KERN_INFO "Move left by = %d\n",moveby);
			    write_command(LCD_ON);
			    printk(KERN_INFO "column before = %d \n",column);
			    column-=moveby;
			    printk(KERN_INFO "column here = %d \n",column);
			    column%=80;
			    if (column<0)
				{
				    column+=80;
				}	
			    printk(KERN_INFO "column after= %d \n",column);		    
			    write_command(0x80^columns[column]);
			    write_command(LCD_ON | LCD_CURSOR_ON);
			}
		    } // end of move cursor
		    else if(t[i+1] == 'C' || t[i+1] == 'c'){
			if((t[i+2] == 'L' || t[i+2] == 'l') &&
			   (t[i+3] == 'R' || t[i+3] == 'r'))
			    write_command(LCD_CLEAR);
			j=i+5;
		    }// end of clear
		    else if(t[i+1] == 'Z' || t[i+1] == 'z'){
			if((t[i+2] == 'O' || t[i+2] == 'o')) {
			    if(t[i+3] == 'n' || t[i+3] == 'N')
				{
				    //cursor on
				    printk(KERN_INFO "cursor on");
				    write_command(LCD_ON | LCD_CURSOR_ON);
				    j=i+5;
				}
			    else if(t[i+3] == 'f' || t[i+3] == 'F')
				{
				    //cursor off	
				    printk(KERN_INFO "cursor off");
				    write_command(LCD_ON);
				    j=i+5;
				}
			}
		    }// end of cursor on/off
		    else if(t[i+1] == 'd' || t[i+1] == 'D'){
			if((t[i+2] == 'O' || t[i+2] == 'o')) {
			    if(t[i+3] == 'n' || t[i+3] == 'N')
				{
				    //Display on
				    printk(KERN_INFO "Display on");
				    write_command(0x0c);
				    j=i+5;
				}
			    else if(t[i+3] == 'f' || t[i+3] == 'F')
				{
				    //Display off	
				    printk(KERN_INFO "Display off");
				    write_command(0x08);
				    j=i+5;
				}
			}
		    }// end of display on/off
		    else if(t[i+1] == 'b' || t[i+1] == 'B'){
			if((t[i+2] == 'O' || t[i+2] == 'o')) {
			    if(t[i+3] == 'n' || t[i+3] == 'N')
				{
				    //Backlight on
				    printk(KERN_INFO "Backlight on");
				    lcd_e = 0x08;
				    lcd_en = 0x09;
				    lcd_rs = 0x0C;
				    outb((lcd_e ^ LCD_MASK),port+2);
				    mdelay(1);
				    j=i+5;
				}
			    else if(t[i+3] == 'f' || t[i+3] == 'F')
				{
				    //Backlight off	
				    printk(KERN_INFO "Backlight off");
				    lcd_e = 0x00;
				    lcd_en = 0x01;
				    lcd_rs = 0x04;
				    outb((lcd_e ^ LCD_MASK),port+2);
				    mdelay(1);
				    j=i+5;
				}
			}	 
		    } // end of backlight on/off 
		    else if((t[i+1] == 'S' || t[i+1] == 's')
			    && (t[i+2] == 'C' || t[i+2] == 'c')
			    && (t[i+3] == 'R' || t[i+3] == 'r')
			    && (t[i+4] == 'O' || t[i+4] == 'o')){
			if(t[i+5] == 'N' || t[i+5] == 'n'){
			    j=i+7;
			    scrolling = 1;
			}
			else if(t[i+5] == 'F' || t[i+5] == 'f') {
			    j=i+7;
			    scrolling = 0;
			    column = 0;
			    /* Set 8-bit mode */
			    write_command(LCD_FCNT_SET);
			    /* LCD on and cursor on */
			    write_command(LCD_ON | LCD_CURSOR_ON);
			    /* sets entry mode and cursor moves right */
			    write_command(LCD_ENTRY | 0x02);
			    write_command(LCD_CLEAR);
			    del_timer_sync(&timer);
			}
		    }
		}
	    }
    }
    return j;
}

void do_scroll(void)
{
    int i=0;
    current_display = current_start;
    current_line=0;
    write_command(LCD_OFF);
    write_command(LCD_CLEAR);
    for(i=0;i<80;i++) 
	{
	    if(current_display == count)
		current_display = temporary;
	    if(buf[current_display]=='\n')
		{
		    if(current_line==0)
			{
			    if(current_display+1<count)
				current_start=current_display+1;
			    else
				current_start=temporary;	
			    i=19;
			    write_command(0x80^columns[i+1]);
			    printk(KERN_INFO "current_start YEs New LiNe = %d\n",current_start);
			    //second_display=current_display;
			}
		    if(current_line==1){
			i=39;
			write_command(0x80^columns[i+1]);
		    }
		    else if(current_line == 2){
			i=59;
			write_command(0x80^columns[i+1]);
		    }
		    else if(current_line == 3){
			i=90;
			write_command(0x80^columns[0]);
		    }
		    //printk(KERN_INFO "when new line i = %d\n",i);
		    current_display++;
		    current_line++;			
		}
	    else{
		outb((lcd_en | lcd_rs) ^ LCD_MASK, port + 2);	// set EN to 1 and RS to 1 
		mdelay(1);
		//outb(buf[current_display],port);
		
		//printk(KERN_INFO "%c\n",buf[current_display]);
		outb(buf[current_display],port);
		//mdelay(2);
		printk(KERN_INFO "buf[current_display] = %c\tcurrent_line = %d"
		       "\tcurrent_display=%d\ti=%d\n",buf[current_display],current_line,current_display,i);
		if(i==19){
		    if(current_display+1<count)
			current_start=current_display+1;
		    else
			current_start=temporary;
		    printk(KERN_INFO "current_start(no newline) = %d\n",current_start);
		}
		if(i==19 ||  i==39 || i==59)
		    {	
			current_line++;
			write_command(0x80^columns[i+1]);
		    }
		if(i==79)
		    {
			i=99;	
			write_command(0x80^columns[0]);
		    }
		mdelay(1);
		outb(lcd_rs ^ LCD_MASK, port + 2);
		mdelay(1);
		current_display++;
	    }
	}
    printk(KERN_INFO "\n");
    write_command(LCD_ON);
    if(scrolling)
	mod_timer(&timer,jiffies+HZ*2);
}

int lcd_open (struct inode *inode, struct file *filp)
{
    minor = iminor(inode);
    //printk(KERN_INFO "Open----------------------\n");
    return 0;
}


int lcd_release (struct inode *inode, struct file *filp)
{
    return 0;
}


/* first, the port-oriented device */


//ssize_t do_lcd_read (struct inode *inode, struct file *filp, char __user *buf,
//size_t count, loff_t *f_pos)
//{

//}


/*
 * Version-specific methods for the fops structure.  FIXME don't need anymore.
 //*/
//ssize_t lcd_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
//{
//return do_lcd_read(filp->f_dentry->d_inode, filp, buf, count, f_pos);
//}

ssize_t lcd_write(struct file *filp1, const char __user *buffer, size_t cnt,
		  loff_t *f_pos)
{
    //char *buf;
    //char p;
    int i,j=0;;
    //inode=*(filp1->f_dentry->d_inode);
    //minor = iminor(&inode);
    //printk(KERN_INFO "WRITE----------------------\n");
    scrolling = 0;
    del_timer_sync(&timer);
    kfree(buf);
    if( (buf = kmalloc((int)cnt,GFP_KERNEL))==NULL )
	{
	    printk(KERN_INFO "FAIL\n");
	    return -1;
	}	
    if (copy_from_user(buf, buffer, cnt)) {
	return -EFAULT;
    }
    
    count = cnt;
    //printk(KERN_INFO "cout=%d\nbuf copied user is = %s\n",(int)cnt,buf);
    //printk(KERN_INFO "buffer send in = %s\n",buffer);
    
    temporary=j=parse_cmd(buf);
    /* this avoids the user setting another command while scrolling is on */
    /* user has to turn it off then its allowed to use w/e command */
    //if(j==-1)
    //return -1;
    
    /* if scrolling is set it adds the timer and sets variables needed */
    if(scrolling == 1){
    	column = 0;
    	current_start = temporary;
    	current_display=0;
    	timer.function = (void *) do_scroll;
	timer.expires = jiffies + HZ/2;
	add_timer(&timer);
    }
    else
	{
	    for(i=j;i<cnt;i++)
		{
		    //printk(KERN_INFO "I=%d\tcolumn=%d\n",i,column);
		    /*
		     * this series of if statements are in charge of moving cursor
		     * to right position. If it wrote 20 continuous characters
		     * then it moves the cursor with it.
		     * But if a new line was encountered then it forces columns to
		     * be 20 and again moves the cursor of display to correct line
		     */
		    if((column < 20 && buf[i] == '\n') || column == 20){
			write_command(0xC0);
			//printk(KERN_INFO "JUMPING TO LINE 2\n");
			column = 20;
		    }
		    else if ((column < 40 && buf[i] == '\n') || column == 40){
			write_command(0x94);
			//printk(KERN_INFO "JUMPING TO LINE 3\n");
			column = 40;
		    }
		    else if ((column < 60 && buf[i] == '\n') || column == 60){
			write_command(0xD4);
			//printk(KERN_INFO "JUMPING TO LINE 4\n");
			column = 60;
		    }
		    else if ((column < 80 && buf[i] == '\n') || column == 80){
			column = 0;
			write_command(0x80);
			//printk(KERN_INFO "JUMPING TO LINE 1\n");
			break;
		    }
		    if(buf[i] != '\n') {
			outb((lcd_en | lcd_rs) ^ LCD_MASK, port + 2);
			mdelay(1);
			outb(buf[i],port);
			mdelay(1);
			outb(lcd_rs ^ LCD_MASK, port + 2);
			mdelay(1);
			column++;
		    }
		}
	}
    return  cnt;
}

unsigned int lcd_poll(struct file *filp, poll_table *wait)
{
    return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}


struct file_operations lcd_fops = {
	.owner	 = THIS_MODULE,
	//.read	 = lcd_read,
	.write	 = lcd_write,
	.poll	 = lcd_poll,
	.open	 = lcd_open,
	.release = lcd_release,
};

void write_command(char command)
{
    outb(((lcd_e) ^ LCD_MASK),port+2);
    mdelay(1);
    outb((lcd_en ^ LCD_MASK),port+2);
    mdelay(1);
    outb(command, port);  
    mdelay(1);
    outb((lcd_e ^ LCD_MASK), port+ 2);
    mdelay(1);
    if (command == LCD_CLEAR) {
	column = 0;
	mdelay(2);
    }
    else if(command == 0x1c) {
	mdelay(1);
    }
}

/* Finally, init and cleanup */

int lcd_init(void)
{
    int result;
    /*
     * first, sort out the base/lcd_base ambiguity: we'd better
     * use lcd_base in the code, for clarity, but allow setting
     * just "base" at load time. 
     */
    lcd_base = base;
    
    /* Get our needed resources. */
    //if (!use_mem) {
    if (! request_region(lcd_base, LCD_NR_PORTS, "lcd")) {
	printk(KERN_INFO "lcd: can't get I/O port address 0x%lx\n",
	       lcd_base);
	return -ENODEV;
    }
    /* also, ioremap it */
    lcd_base = (unsigned long) ioremap(lcd_base, LCD_NR_PORTS);
    /* Hmm... we should check the return value */
    
    /* Here we register our device - should not fail thereafter */
    result = register_chrdev(major, "lcd", &lcd_fops);
    if (result < 0) {
	printk(KERN_INFO "lcd: can't get major number\n");
	release_region(lcd_base,LCD_NR_PORTS);  /* FIXME - use-mem case? */
	return result;
    }
    if (major == 0) major = result; /* dynamic */
    
    /* Set 8-bit mode */
    write_command(LCD_FCNT_SET);
    /* LCD on and cursor on */
    write_command(LCD_ON | LCD_CURSOR_ON);
    /* sets entry mode and cursor moves right */
    write_command(LCD_ENTRY | 0x02);
    write_command(LCD_CLEAR);
    column = 0;
    init_timer(&timer);
    temporary = 0;
    return 0;
}

void lcd_cleanup(void)
{
    lcd_base = base;
    printk(KERN_INFO "CLEAN up done----------------------\n");
    release_region(lcd_base,LCD_NR_PORTS);
    unregister_chrdev(major, "lcd");
    //if (use_mem) {
    //iounmap((void __iomem *)lcd_base);
    //release_mem_region(lcd_base, LCD_NR_PORTS);
    //} else {
    
    //}

    /* turning backlight off */
    lcd_e = 0x00;
    lcd_en = 0x01;
    lcd_rs = 0x04;
    outb((lcd_e ^ LCD_MASK),port+2);
    mdelay(1);
    write_command(LCD_CLEAR);
    write_command(LCD_OFF);
    /* deleting timer */
    del_timer_sync(&timer);
    temporary = 0;
    /* freeing the memory */
    kfree(buf);
}

module_init(lcd_init);
module_exit(lcd_cleanup);
